/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package analisis;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import reticulado.PointsToGraph;
import soot.Body;
import soot.PhaseOptions;
import soot.Scene;
import soot.SootClass;
import soot.SootMethod;
import soot.Unit;
import soot.jimple.Stmt;
import soot.jimple.toolkits.callgraph.CHATransformer;
import soot.jimple.toolkits.callgraph.CallGraph;
import soot.jimple.toolkits.callgraph.Edge;
import soot.options.Options;
import soot.toolkits.graph.BriefUnitGraph;
import soot.toolkits.scalar.FlowSet;

/**
 *
 * @author Santiago Avendaño
 */
public class InterproceduralCFGAnalysis {

    private CallGraph callGraph;
    private List<SootMethod> methods;
    private Map<Stmt, List<SootMethod>> methodsCalls;
    private Map<SootMethod, BriefUnitGraph> methodsCFGs;
    private PointsToGraph resultado;
    private SootMethod metodoPrincipal;

    public InterproceduralCFGAnalysis(String nombreClase, String nombreMetodo) {
        Options.v().set_keep_line_number(true);
        PhaseOptions.v().setPhaseOption("jb", "use-original-names:true");
        resultado = analyzeMethod(nombreClase, nombreMetodo);
    }

    private PointsToGraph analyzeMethod(String clazz, String method) {

        //Seteo la clase y el método a analizar
        Options.v().set_main_class(clazz);
        SootClass c = Scene.v().loadClassAndSupport(clazz);
        c.setApplicationClass();
        metodoPrincipal = c.getMethodByName(method);

        //Obtengo el CallGraph del metodo invocado
        CHATransformer.v().transform();
        callGraph = Scene.v().getCallGraph();

        // Seteo los campos necesario para construir el Supergraph
        methods = new ArrayList<SootMethod>();
        methodsCalls = new HashMap<Stmt, List<SootMethod>>();
        methodsCFGs = new HashMap<SootMethod, BriefUnitGraph>();

        //guardo los datos del callgraph
        walkCallGraph(metodoPrincipal);

        //Se calculan todos los CFG de los metodos
        for (SootMethod m : methods) {
            Body b = m.retrieveActiveBody();
            methodsCFGs.put(m, new BriefUnitGraph(b));
        }

        //genero supergraph, todos los CFGs conectados
        SuperGraph.methodsCFGs = methodsCFGs;
        SuperGraph.methodsCalls = methodsCalls;
        SuperGraph superGraph = new SuperGraph(metodoPrincipal.retrieveActiveBody());

        //Corro el analisis Intraprocedural
        IntraProceduralAnalysis intra = new IntraProceduralAnalysis(superGraph, null, null, null, null);
        FlowSet in = null;
        FlowSet out = null;
        for (Unit unit : superGraph) {
            in = intra.getFlowBefore(unit);
            out = intra.getFlowAfter(unit);

            if (unit == SuperGraph.mainMethodExitPoint) {
                break;
            }
        }
        PointsToGraph ptg = null;
        if (out == null) {
            System.out.println("Null!");
        } else {
            ptg = (PointsToGraph) out;
        }
        return ptg;
    }

    //analiza el callgraph y extrae la info necesaria
    private void walkCallGraph(SootMethod m) {
        //ignoramos constructores y métodos ya recorridos
        if (!methods.contains(m) && !m.getName().equals("<init>")) {
            methods.add(m);

            Iterator<Edge> edges = callGraph.edgesOutOf(m);
            while (edges.hasNext()) {
                Edge edge = edges.next();
                SootMethod om = edge.getTgt().method();
                Stmt stmt = edge.srcStmt();
                if (edge.isExplicit()) {
                    if (methodsCalls.get(stmt) == null) {
                        methodsCalls.put(stmt, new ArrayList<SootMethod>());
                    }
                    methodsCalls.get(stmt).add(om);

                    walkCallGraph(om);
                }
            }
        }
    }

    public PointsToGraph getResult() {
        return resultado;
    }

    public SootMethod getMetodoPrincipal() {
        return metodoPrincipal;
    }

}
